import javax.xml.bind.annotation.XmlRootElement;

import codeg.CodeGeneration;
import codeg.CodeGenerationContext;
import codeg.Codeg;
import codeg.CodegException;

@XmlRootElement
public class WorldCodeg extends Codeg
{
    static class Color {
        public int red;
        public int green;
        public int blue;
        
        public Color() {}
        
        public Color(int r, int g, int b) {
            red = r;
            green = g;
            blue = b;
        }
    }
    
	private int width = 640;
	private int height = 480;
	private Color color = new Color(0, 128, 0);
	
    public int getWidth() {
        return width;
    }
    public void setWidth(int width) {
        this.width = width;
    }
    public int getHeight() {
        return height;
    }
    public void setHeight(int height) {
        this.height = height;
    }
    public Color getColor() {
        return color;
    }
    public void setColor(Color color) {
        this.color = color;
    }
    
    public String[] getRoles() {
        return new String[] { "locyan:world" };
    }
    
    public CodeGeneration getCode(CodeGenerationContext context) throws CodegException {
        CodeGeneration code = new CodeGeneration(this);
        
        /* This codeg only generates Java code currently */
        if (context.getLanguage() != CodeGenerationContext.LANGUAGE.JAVA)
            return code;
        
        code.setImports(new String[] {
        "javax.swing.JPanel", 
        "java.awt.Color",
        "java.awt.Dimension",
        "java.awt.Graphics",
        "java.awt.Point",
        "java.util.Hashtable",
        "java.util.LinkedList",
        "java.util.Set"
        });
        
        code.setGlobalCode(
        "/**\n"+
        " * A world in which actors can act.\n"+
        " */\n"+
        "class World extends JPanel {\n"+
        "    private static final long serialVersionUID = 1L;\n"+
        "    private int width;\n"+
        "    private int height;\n"+
        "\n"+
        "    /**\n"+
        "     * A movement representing a destination and the pen state\n"+
        "     */\n"+
        "    class Movement {\n"+
        "        public Point destination;\n"+
        "        public boolean penDown;\n"+
        "        \n"+
        "        Movement(Point destination, boolean penDown) {\n"+
        "            this.destination = destination;\n"+
        "            this.penDown = penDown;\n"+
        "        }\n"+
        "    }\n"+
        "    \n"+
        "    public Hashtable<Actor, LinkedList<Movement>> actors = new Hashtable<Actor, LinkedList<Movement>>();\n"+
        "    \n"+
        "    /**\n"+
        "     * Creates a new world object with (width, height) dimensions and a background color.\n"+
        "     * @param width The width of the world in pixels.\n"+
        "     * @param height The height of the world in pixels.\n"+
        "     * @param color The background color.\n"+
        "     */\n"+
        "    public World(int width, int height, Color color) {\n"+
        "        this.width = width;\n"+
        "        this.height = height;\n"+
        "        this.setBackground(color);\n"+
        "    }\n"+
        "\n"+
        "    public void addActor(Actor actor) {\n"+
        "        LinkedList<Movement> path = new LinkedList<Movement>();\n"+
        "        path.add(new Movement(actor.getPosition(), actor.isPenDown()));\n"+
        "        actors.put(actor, path);\n"+
        "        actor.setWorld(this);\n"+
        "    }\n"+
        "    \n"+
        "    public Set<Actor> getActors() {\n"+
        "        return actors.keySet();\n"+
        "    }\n"+
        "    \n"+
        "    public Dimension getPreferredSize() {\n"+
        "        return new Dimension(width, height);\n"+
        "    }\n"+
        "    \n"+
        "    public void paintComponent(Graphics g) {\n"+
        "        super.paintComponent(g);       \n"+
        "\n"+
        "        LinkedList<Movement> path;\n"+
        "        for (Actor actor : actors.keySet()) {\n"+
        "            path = actors.get(actor);\n"+
        "            g.setColor(actor.getColor());\n"+
        "            int x[] = new int[path.size()];\n"+
        "            int y[] = new int[path.size()];\n"+
        "            \n"+
        "            int i = 0;\n"+
        "            for (Movement movement : path) {\n"+
        "                if (!movement.penDown) {\n"+
        "                    g.drawPolyline(x, y, i);\n"+
        "                    x = new int[path.size() - i];\n"+
        "                    y = new int[path.size() - i];\n"+
        "                    x[0] = movement.destination.x;\n"+
        "                    y[0] = movement.destination.y;\n"+
        "                    i = 1;\n"+
        "                }\n"+
        "                else {\n"+
        "                    x[i] = movement.destination.x;\n"+
        "                    y[i] = movement.destination.y;\n"+
        "                    i++;\n"+
        "                }\n"+
        "            }\n"+
        "            \n"+
        "            g.drawPolyline(x, y, i);\n"+
        "        }\n"+
        "        \n"+
        "        for (Actor actor : actors.keySet()) {\n"+
        "            actor.paint(g);\n"+
        "        }\n"+
        "    }\n"+
        "\n"+
        "    public void registerMovement(Actor actor, Point point, boolean penDown) {\n"+
        "        actors.get(actor).add(new Movement(point, penDown));\n"+
        "    }\n"+
        "}");

        Color c = getColor();
        String s = "JFrame frame = new JFrame(\"Locyan\");\n"+
        "frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);\n"+
        "World " + getId() + " = new World(" + getWidth() + ", " + getHeight() + ", " + "new Color(" + c.red + ", " + c.green + ", " + c.blue + "));\n"+
        "// Add the list of actors declared in the world codeg\n";

        // Find actors and add them to the simulation
        for (Codeg codeg : context.getCodegsList())
            if (codeg.hasRole("locyan:actor"))
                s += getId() + ".addActor(" + codeg.getInformation("locyan:id") + ");\n";
            else if (codeg != this && codeg.hasRole("locyan:world"))
                throw new CodegException("The world codeg must be the only of its type in a file", CodegException.UNKNOWN_ERROR);

        s += "// Run the world step by step\n"+
        "frame.add(" + getId() + ");\n"+
        "frame.setSize(" + getWidth() + "," + getHeight() + ");\n"+
        "frame.setVisible(true);\n"+
        "for (Actor x : " + getId() + ".getActors())\n"+
        "    x.start();\n"+
        "boolean done = false;\n"+
        "while (!done) {\n"+
        "    done = true;\n"+
        "    Set<Actor> actors = " + getId() + ".getActors();\n"+
        "    for (Actor actor : actors) {\n"+
        "        if (actor.isAlive()) {\n"+
        "            actor.next();\n"+
        "            try { Thread.sleep(100); }\n"+
        "            catch (InterruptedException e) {}\n"+
        "        }\n"+
        "        if (done && actor.isAlive())\n"+
        "            done = false;\n"+
        "    }\n"+
        "}\n";
        code.setLocalCode(s);
        
        return code;
    }
}